/*
* This file is part of Project SkyFire https://www.projectskyfire.org.
* See LICENSE.md file for Copyright information
*/

#ifndef SKYFIRE_INSTANCE_DATA_H
#define SKYFIRE_INSTANCE_DATA_H

#include "ObjectMgr.h"
#include "World.h"
#include "WorldStateBuilder.h"
#include "ZoneScript.h"

#define OUT_SAVE_INST_DATA             SF_LOG_DEBUG("scripts", "Saving Instance Data for Instance %s (Map %d, Instance Id %d)", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
#define OUT_SAVE_INST_DATA_COMPLETE    SF_LOG_DEBUG("scripts", "Saving Instance Data for Instance %s (Map %d, Instance Id %d) completed.", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
#define OUT_LOAD_INST_DATA(a)          SF_LOG_DEBUG("scripts", "Loading Instance Data for Instance %s (Map %d, Instance Id %d). Input is '%s'", instance->GetMapName(), instance->GetId(), instance->GetInstanceId(), a)
#define OUT_LOAD_INST_DATA_COMPLETE    SF_LOG_DEBUG("scripts", "Instance Data Load for Instance %s (Map %d, Instance Id: %d) is complete.", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
#define OUT_LOAD_INST_DATA_FAIL        SF_LOG_ERROR("scripts", "Unable to load Instance Data for Instance %s (Map %d, Instance Id: %d).", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())

class Map;
class Unit;
class Player;
class GameObject;
class Creature;

typedef std::set<GameObject*> DoorSet;
typedef std::set<Creature*> MinionSet;

enum EncounterFrameType
{
    ENCOUNTER_FRAME_SET_COMBAT_RES_LIMIT = 0,
    ENCOUNTER_FRAME_RESET_COMBAT_RES_LIMIT = 1,
    ENCOUNTER_FRAME_ENGAGE = 2,
    ENCOUNTER_FRAME_DISENGAGE = 3,
    ENCOUNTER_FRAME_UPDATE_PRIORITY = 4,
    ENCOUNTER_FRAME_ADD_TIMER = 5,
    ENCOUNTER_FRAME_ENABLE_OBJECTIVE = 6,
    ENCOUNTER_FRAME_UPDATE_OBJECTIVE = 7,
    ENCOUNTER_FRAME_DISABLE_OBJECTIVE = 8,
    ENCOUNTER_FRAME_UNK7 = 9,    // Seems to have something to do with sorting the encounter units
    ENCOUNTER_FRAME_ADD_COMBAT_RES_LIMIT = 10
};

enum EncounterState
{
    NOT_STARTED = 0,
    IN_PROGRESS = 1,
    FAIL = 2,
    DONE = 3,
    SPECIAL = 4,
    TO_BE_DECIDED = 5
};

enum DoorType
{
    DOOR_TYPE_ROOM = 0,    // Door can open if encounter is not in progress
    DOOR_TYPE_PASSAGE = 1,    // Door can open if encounter is done
    DOOR_TYPE_SPAWN_HOLE = 2,    // Door can open if encounter is in progress, typically used for spawning places
    MAX_DOOR_TYPES
};

enum BoundaryType
{
    BOUNDARY_NONE = 0,
    BOUNDARY_N,
    BOUNDARY_S,
    BOUNDARY_E,
    BOUNDARY_W,
    BOUNDARY_NE,
    BOUNDARY_NW,
    BOUNDARY_SE,
    BOUNDARY_SW,
    BOUNDARY_MAX_X = BOUNDARY_N,
    BOUNDARY_MIN_X = BOUNDARY_S,
    BOUNDARY_MAX_Y = BOUNDARY_W,
    BOUNDARY_MIN_Y = BOUNDARY_E
};

typedef std::map<BoundaryType, float> BossBoundaryMap;

struct DoorData
{
    uint32 entry, bossId;
    DoorType type;
    uint32 boundary;
};

struct MinionData
{
    uint32 entry, bossId;
};

struct BossInfo
{
    BossInfo() : state(TO_BE_DECIDED) { }
    EncounterState state;
    DoorSet door[MAX_DOOR_TYPES];
    MinionSet minion;
    BossBoundaryMap boundary;
};

struct DoorInfo
{
    explicit DoorInfo(BossInfo* _bossInfo, DoorType _type, BoundaryType _boundary)
        : bossInfo(_bossInfo), type(_type), boundary(_boundary) { }
    BossInfo* bossInfo;
    DoorType type;
    BoundaryType boundary;
};

struct MinionInfo
{
    explicit MinionInfo(BossInfo* _bossInfo) : bossInfo(_bossInfo) { }
    BossInfo* bossInfo;
};

typedef std::multimap<uint32 /*entry*/, DoorInfo> DoorInfoMap;
typedef std::pair<DoorInfoMap::const_iterator, DoorInfoMap::const_iterator> DoorInfoMapBounds;

typedef std::map<uint32 /*entry*/, MinionInfo> MinionInfoMap;

class InstanceScript : public ZoneScript
{
public:
    explicit InstanceScript(Map* map) : instance(map), completedEncounters(0) { }

    virtual ~InstanceScript() { }

    Map* instance;

    //On creation, NOT load.
    virtual void Initialize() { }

    //On load
    virtual void Load(char const* data) { LoadBossState(data); }

    //When save is needed, this function generates the data
    virtual std::string GetSaveData() { return GetBossSaveData(); }

    void SaveToDB();

    virtual void Update(uint32 /*diff*/) { }

    //Used by the map's CanEnter function.
    //This is to prevent players from entering during boss encounters.
    virtual bool IsEncounterInProgress() const;

    //Called when a player successfully enters the instance.
    virtual void OnPlayerEnter(Player* /*player*/) { }

    //Handle open / close objects
    //use HandleGameObject(0, boolen, GO); in OnObjectCreate in instance scripts
    //use HandleGameObject(GUID, boolen, NULL); in any other script
    void HandleGameObject(uint64 guid, bool open, GameObject* go = NULL);

    //change active state of doors or buttons
    void DoUseDoorOrButton(uint64 guid, uint32 withRestoreTime = 0, bool useAlternativeState = false);

    //Respawns a GO having negative spawntimesecs in gameobject-table
    void DoRespawnGameObject(uint64 guid, uint32 timeToDespawn = MINUTE);

    //sends world state update to all players in instance
    void DoUpdateWorldState(uint32 worldstateId, uint32 worldstateValue);

    // Send Notify to all players in instance
    void DoSendNotifyToInstance(char const* format, ...);

    // Update Achievement Criteria for all players in instance
    void DoUpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscValue1 = 0, uint32 miscValue2 = 0, Unit* unit = NULL);

    // Start/Stop Timed Achievement Criteria for all players in instance
    void DoStartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry);
    void DoStopTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry);

    // Remove Auras due to Spell on all players in instance
    void DoRemoveAurasDueToSpellOnPlayers(uint32 spell);

    // Cast spell on all players in instance
    void DoCastSpellOnPlayers(uint32 spell);

    // Return wether server allow two side groups or not
    bool ServerAllowsTwoSideGroups() { return sWorld->GetBoolConfig(WorldBoolConfigs::CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP); }

    virtual bool SetBossState(uint32 id, EncounterState state);
    EncounterState GetBossState(uint32 id) const { return id < bosses.size() ? bosses[id].state : TO_BE_DECIDED; }
    BossBoundaryMap const* GetBossBoundary(uint32 id) const { return id < bosses.size() ? &bosses[id].boundary : NULL; }

    // Achievement criteria additional requirements check
    // NOTE: not use this if same can be checked existed requirement types from AchievementCriteriaRequirementType
    virtual bool CheckAchievementCriteriaMeet(uint32 /*criteria_id*/, Player const* /*source*/, Unit const* /*target*/ = NULL, uint32 /*miscvalue1*/ = 0);

    // Checks boss requirements (one boss required to kill other)
    virtual bool CheckRequiredBosses(uint32 /*bossId*/, Player const* /*player*/ = NULL) const { return true; }

    // Checks encounter state at kill/spellcast
    void UpdateEncounterState(EncounterCreditType type, uint32 creditEntry, Unit* source);

    // Used only during loading
    void SetCompletedEncountersMask(uint32 newMask) { completedEncounters = newMask; }

    // Returns completed encounters mask for packets
    uint32 GetCompletedEncounterMask() const { return completedEncounters; }

    void SendEncounterUnit(uint32 type, Unit* unit = NULL, uint8 param1 = 0, uint8 param2 = 0);

    virtual void FillInitialWorldStates(WorldStateBuilder& /*builder*/) { }

    // ReCheck PhaseTemplate related conditions
    void UpdatePhasing();

protected:
    void SetBossNumber(uint32 number) { bosses.resize(number); }
    void LoadDoorData(DoorData const* data);
    void LoadMinionData(MinionData const* data);

    void AddDoor(GameObject* door, bool add);
    void AddMinion(Creature* minion, bool add);

    void UpdateDoorState(GameObject* door);
    void UpdateMinionState(Creature* minion, EncounterState state);

    void LoadBossState(char const* data);
    std::string GetBossSaveData();
private:
    std::vector<BossInfo> bosses;
    DoorInfoMap doors;
    MinionInfoMap minions;
    uint32 completedEncounters; // completed encounter mask, bit indexes are DungeonEncounter.dbc boss numbers, used for packets
};

template<class AI, class T>
AI* GetInstanceAI(T* obj, char const* scriptName)
{
    if (InstanceMap* instance = obj->GetMap()->ToInstanceMap())
        if (instance->GetInstanceScript())
            if (instance->GetScriptId() == sObjectMgr->GetScriptId(scriptName))
                return new AI(obj);

    return NULL;
}

#endif // SKYFIRE_INSTANCE_DATA_H
